package web.dao;

import java.beans.PropertyDescriptor;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

import web.model.UUID;

public abstract class GenericDao<T> extends BaseDAO {
	
	protected static final Object EQUAL = "=";
	protected static final Object QUOTE = "'";
	protected static final Object COMMA = ",";
	protected static final Object SPACE = " ";

	private Class<T> persistentClass;

	@SuppressWarnings("unchecked")
	public GenericDao() {
		/** extract generic class parameter of the dao successor class */
		persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	protected String getTableName() {
		String tableName = persistentClass.getSimpleName();
		if (tableName.endsWith("Bean")) {
			tableName = tableName.substring(0, tableName.length() - "Bean".length());
		}
		return tableName.toLowerCase();
	}

	@SuppressWarnings("unchecked")
	protected Collection<T> executeQuery(String sql) {
		Collection<T> result;
		result = (Collection<T>) executeQuery(persistentClass, sql);
		return result;
	}

	/**
	 * Get all.
	 * @return
	 */
	public Collection<T> findAll() {
		return findByCriteria();
	}

	public Collection<T> findByProperty(String propertyName, Object value) {
		log.debug("finding " + persistentClass + " instance with property: " + propertyName + ", value: " + value);
		Criteria criteria = Criteria.equal(propertyName, value);
		return findByCriteria(criteria);
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	protected Collection<T> findByCriteria(Criteria... criteria) {
		Query query = getSession().createQuery(getTableName(), persistentClass);
		for (Criteria c : criteria) {
			query.addSubQueries(c);
		}
		return query.list();
	}

	/**
	 * search similar objects
	 * @param exampleInstance
	 * @param excludePropertyNames
	 * @return list of similar objects
	 */
	public Collection<T> findByExample(T exampleInstance, Collection<String> excludePropertyNames) {
		Query query = getSession().createQuery(getTableName(), persistentClass);
		/** extract properties */
		if (excludePropertyNames == null) {
			excludePropertyNames = new HashSet<String>();
		}
		excludePropertyNames.add("id");
		Collection<PropertyDescriptor> propertyDescriptors = getProperties(persistentClass, excludePropertyNames);
		/** prepare sql parameters */
		Criteria where = null;
		for (PropertyDescriptor property : propertyDescriptors) {
			Object value = getValue(property, exampleInstance);
			if (null != value) {
				if ((value instanceof Collection<?>) && (0 == ((Collection<?>)value).size())) {
					continue;
				}
				Criteria more = null;
				if (value instanceof Collection<?>) { // Iterable
					/** list parameters */
					//log.debug("set - " + property.getName());
					more = Criteria.inList(property.getName(), (Collection<?>)value);
				} else {
					/** plain parameters */
					more = Criteria.equal(property.getName(), value);
					//log.debug(property.getName() + " - " + property.getPropertyType() + " - " + value);
				}
				if (where != null) {
					where = Criteria.and(where, more);
				} else {
					where = more;
				}
			}
		}
		query.addSubQueries(where);
		/**/log.debug("sql: " + query.getQueryText());
		/** query */
		return query.list();
	}

	protected T find(T bean) {
		Collection<T> beans = findByExample(bean, null);
		if ((beans != null) && beans.iterator().hasNext()) {
			return beans.iterator().next();
		}
		return null;
	}

	public int saveOrUpdate(T bean) {
		T old = find(bean);
		if (old != null) {
			Object id = getValue(getProperty(persistentClass, getId()), old);
			setValue(getProperty(persistentClass, getId()), bean, id);
			return update(bean);
		} else {
			return insert(bean);
		}
	}

	public int insert(T bean) {
		StringBuffer sql = new StringBuffer();
		sql.append("INSERT INTO ");
		sql.append(getTableName());
		sql.append(" (");
		StringBuffer values = new StringBuffer();
		values.append(" VALUES (");
		Iterator<PropertyDescriptor> i = getProperties(persistentClass).iterator();
		while (i.hasNext()) {
			PropertyDescriptor property = i.next();
			sql.append(property.getName());
			if (i.hasNext()) {
				sql.append(COMMA);
				sql.append(SPACE);
			}
			values.append(QUOTE);
			Object value = getValue(property, bean);
			values.append(value.toString());
			values.append(QUOTE);
			if (i.hasNext()) {
				values.append(COMMA);
				values.append(SPACE);
			}
		}
		sql.append(")");
		sql.append(values);
		sql.append(")");
		int result = executeUpdate(sql.toString());
		return result;
	}

	public int update(T bean) { // TODO insert to history also
		StringBuffer sql = new StringBuffer();
		sql.append("UPDATE ");
		sql.append(getTableName());
		sql.append(" SET ");
		Iterator<PropertyDescriptor> i = getProperties(persistentClass).iterator();
		while (i.hasNext()) {
			PropertyDescriptor property = i.next();
			sql.append(property.getName());
			sql.append(EQUAL);
			sql.append(QUOTE);
			Object value = getValue(property, bean);
			sql.append(value.toString());
			sql.append(QUOTE);
			if (i.hasNext()) {
				sql.append(COMMA);
				sql.append(SPACE);
			}
		}
		sql.append(" WHERE ");
		sql.append(getWhere(bean));
		int result = executeUpdate(sql.toString());
		return result;
	}

	public int remove(UUID id) {
		StringBuffer sql = new StringBuffer();
		sql.append("DELETE FROM ");
		sql.append(getTableName());
		sql.append(" WHERE ");
		sql.append(getId());
		sql.append(EQUAL);
		sql.append(QUOTE);
		sql.append(id);
		sql.append(QUOTE);
		int result = executeUpdate(sql.toString());
		return result;
	}

	public int remove(T bean) {
		StringBuffer sql = new StringBuffer();
		sql.append("DELETE FROM ");
		sql.append(getTableName());
		sql.append(" WHERE ");
		sql.append(getWhere(bean));
		int result = executeUpdate(sql.toString());
		return result;
	}

	public String getWhere(T bean) {
		StringBuffer where = new StringBuffer();
		where.append(getId());
		where.append(EQUAL);
		where.append(QUOTE);
		where.append(getValue(getProperty(persistentClass, getId()), bean));
		where.append(QUOTE);
		return where.toString();
	}

	public String getId() {
		return "id";
	}
}
